<?php
if(isset($GLOBALS['context']))	require_lib('openapi/OpenAPIRequest',false);
else	require_once dirname(__FILE__).'/../OpenAPIRequest.class.php';

/* other taobao url
* TAIBAO_API_AUTHCODEHOST:'http://container.api.taobao.com/container?appkey=
*/

/** 
 * 传送淘宝接口[简称taobao]API参数到网店,得到相应返回值。
 * @author alex  
 * @version v1.0  2010-5-30
 */
class TaoBaoRequest extends OpenAPIRequest {
	/**
	 * taobao API的网站地址
	 */	
	const HOST_URL='http://gw.api.taobao.com/router/rest';
	/**
	 * taobao 沙箱测试环境的网站地址
	 */		
	const SANDBOX_URL='http://gw.api.tbsandbox.com/router/rest';
	
	/**
	 * RefreshToken延长淘宝授权时间地址
	 */
	const REFRESH_URL='http://container.open.taobao.com/container/refresh';
	
	/**
	 * @var array $app_token 应用app访问taobao的令牌，参数包括：<br/>
	 * string app_key : 必须，  API应用的标志符.<br/>
	 * string app_secret :必须， 用户登录appSecret，用于用户鉴权，将相关参数通过md5,hmac加密为一个sign值<br/>
	 * string app_session   :可选，淘宝会话，需要会话的接口必须提供 
	 */
	public $app_token=array();
	
	/**
	 * @var array $app_setting 应用app访问taobao的通用参数设置，一般不改变默认值，包括：<br/>
	 * string format：响应格式。xml,json，默认是 'json'。<br/>
	 * strng  sign_method :签名方法，md5,hmac，默认是 'md5'。<br/>
	 * string v：API接口版本号，2.0<br/>
	 * strng  timestamp:时间戳，格式为yyyy-mm-dd hh:mm:ss，例如：2008-01-25 20:23:30。淘宝API服务端允许客户端请求时间误差为10分钟<br/>
	 */
	public $app_setting=array('format'=>'json',     //json , xml
	                       'sign_method'=>'hmac', // md5 , hmac
	                       'v'=>'2.0', 'timestamp'=>'2010-06-01 12:00:00');
	 
	/**
	 * @var bool $in_sandbox 是否在沙箱测试环境下运行，默认false
	 */
	public $in_sandbox=false;
	
	
    function __construct(){
    	parent::__construct();
    	$time_zone=@date_default_timezone_get();
    	if(! $time_zone || $time_zone=='UTC')
    	    date_default_timezone_set('Asia/Shanghai');
    }
    
	/**
	 * 得到taobo API的返回值，JSON,XML字符串。
	 * @param string $cmdid 要调用的api接口的cmdid字符串，如user.getUserInfo，如果为空，将返回false.
	 * @param array $params 要调用的api接口的参数数组，一般通用的参数不要传，如charset，format等。
	 * @return string|bool 调用的api接口得到的JSON,XML字符串，失败将抛出异常。
	 */
	function &get_resp($cmdid,array $params){
		if(! $cmdid) {$a=false; return $a;} //count($paramArr)==0,some function is empty
		if(! $this->app_token['app_key'] || ! $this->app_token['app_secret']) {$a=false; return $a;}
		$this->app_setting['timestamp']=date('Y-m-d H:i:s');
		$host_url=$this->in_sandbox ? self::SANDBOX_URL : self::HOST_URL ;
		
		//组装url,param array.
		$params=$params + $this->app_setting;
		$params['app_key']=$this->app_token['app_key'];
		if($this->app_token['app_session'])
		    $params['session']=$this->app_token['app_session'];
		$params['method']=$cmdid;
		
		
		$params['sign']=$this->_createSign($this->app_token['app_secret'],$params,$this->app_setting['sign_method']);
		$result=& $this->send($host_url,$params);
		return $result;
	}
	
	/**
	 * 得到 API的返回数组，失败将抛出异常。
	 * @param string $cmdid 要调用的api接口的cmdid字符串，如果为空，将返回false.
	 * @param array $params 要调用的api接口的参数数组，一般通用的参数不要传，如charset，format等。
	 * @param string $resp_handle 用于额外处理get_resp函数返回字符串的函数名称，此函数调用在调用get_resp函数后，解析为数组前，默认为NULL不调用。
	 * @return array|false 返回调用的api接口得到的数组，API返回错误返回false，失败将抛出异常。
	 */	
	protected function &get_array($cmdid,array  $params,$resp_handle=NULL){
		//parse xml format
		if($this->app_setting['format']=='xml'){  
			$result=& $this->get_resp($cmdid,$params);
			
			if($resp_handle)  //addition handle result 
				try{$result = $resp_handle($result);}catch (Exception  $e){}
				
			$arr=array(); 
			if(!$this->parseXmlString($result,$arr)) {$f=false; return $f;};
		}else{
			//parse json format
			$result=&$this->get_resp($cmdid,$params);
			if(! $result){$f=false; return $f;}
		
			if($resp_handle)  //addition handle result 
				try{$result = $resp_handle($result);}catch (Exception  $e){}
			$result = preg_replace('/(":)(\d{9,})/i', '${1}"${2}"', $result); //taobao bug,number >2^32
		
			$arr=json_decode($result,true);
			if ($arr == NULL) { //normal kick invalid json
				$patterns = array ('/,+\s*\}/', '/,+\s*\]/', '/"\s+|\s+"/','/\n|\r/' );
				$replacements = array ('}', ']', '"',' ' );
				$result = preg_replace ( $patterns, $replacements, $result );
				$arr = json_decode ( $result, true );
			}
		}
		if (! $this->check_result ( $arr, $this->err_no, $this->err_msg )){
			$msg = $this->err_msg . ";\n{$cmdid}";
			if(isset($GLOBALS['context'])) $GLOBALS['context']->log_error($msg);
			$f=false; return $f;
		}
		if(is_array($arr) && count($arr)>0){
			reset($arr);
			list(,$realarr)=each($arr);			
			return $realarr;
		}else return $arr;	
			
	}	

	/**
	 * 解析淘宝授权后回调传入参数，http://container.api.taobao.com/container?appkey=，参数解释@see http://open.taobao.com/doc/detail.htm?id=110
	 * @param arrya $request 淘宝回调传入参数 ，包括top_appkey,top_session,top_sign,top_parameters
	 * @return false|array 返回结果，如回调非法，返回false。
	 * <br>成功调用返回结果数值，包括：top_appkey,top_session,refresh_token,expires_in,re_expires_in,visitor_id,visitor_nick,sub_visitor_id,sub_visitor_nick
	 */
	function get_authorize_param(array & $request){
            if(! isset($request['top_appkey']) || ! isset($request['top_session']) || ! isset($request['top_parameters'])
                    || ! isset($request['top_sign'])) return false;
			if($request['top_appkey'] != $this->app_token['app_key']) return false;
			$p=$request['top_parameters'];
            $s=base64_encode(md5($request['top_appkey']  . $p . $request['top_session'] . $this->app_token['app_secret'],true));
            if($request['top_sign'] != $s) return false;
           
            
            $result=array();
            $result['top_appkey']=$request['top_appkey'];
            $result['top_session']=$request['top_session'];
                       
            $params=iconv('GBK','UTF-8', base64_decode($p));
            $params=explode('&', $params);
            foreach($params as $param ){
                list($n,$v)=explode('=', $param);
                $result[$n]=$v;
            }
            return $result;
     }	
     /**
      *刷新授权，延长session有效时间
      * @param string $refresh_token    淘宝回调url中refresh_token参数值
      * @return
      */
     function refresh_token($refresh_token){
 		if(! $this->app_token['app_key'] || ! $this->app_token['app_secret'] || ! $this->app_token['app_session']) 
		   return false;
		 //appkey=xxx&refresh_token=xxx &sessionkey=xxx&sign=xxx
		 $p=array('appkey'=>$this->app_token['app_key'],'refresh_token'=>$refresh_token,'sessionkey'=>$this->app_token['app_session']);
		 $s=& $this->_to_sign_str($p);
		 $p['sign']=strtoupper(md5( $s . $this->app_token['app_secret'])); 	
		 $c=$this->use_curl;
		 $this->use_curl=false;
		 $result=& $this->send(self::REFRESH_URL,$p,OpenAPIRequest::HTTPMETHOD_GET);
		 $this->use_curl=$c;
		 return $result;
     }
	/**
	 * 检查接口调用get*函数返回是否有错误
	 * @param  mixed $result  接口调用get*函数返回值.
	 * @return bool true 接口调用有效，false 接口调用出错.
	 */
	protected function check_result(&$result,&$errorCode,&$errorMsg){
		$errorCode=0;
		$errorMsg=''; 		
		if(is_array($result)){
			if(! array_key_exists('error_response',$result))
			    return true;
			else{
				$error=$result['error_response'];
				$errorCode=$error['code'];
				$errorMsg=$error['msg'];
				if(array_key_exists('sub_code',$error))
				   $errorMsg .= ";\n".$error['sub_code'];
				if(array_key_exists('sub_msg',$error))
				   $errorMsg .= ";\n".$error['sub_msg'];
				
			}
		}elseif (is_string($result)){
			$match=array();
			if(! preg_match('/^\{"error_response":\{.+,"code":(\d*),"msg":"(.+)\}\}$/',$result,$match))
			     return true;
			else{
				$errorCode=$match[1];
				$submatch=array();
				if(preg_match('/(.+)","sub_code":"(.*)","sub_msg":"(.*)"/',$match[2],$submatch)) //sub_code and sun_msg all
					$errorMsg = $submatch[1].";\n".$submatch[2].";\n".$submatch[3];
				else $errorMsg=rtrim($match[2],'"');
				
			}
		}
		elseif(is_bool($result))
		 return $result;

		return false;
	}
	/*
	 * 签名函数,内部用 
	 */
    protected function _createSign($app_secret,array & $paramArr,$sign_method='md5') {
		$sign= & $this->_to_sign_str($paramArr);
		if($sign_method==='hmac')
			return strtoupper(hash_hmac('md5',$sign,$app_secret));
		else
			return strtoupper(md5($app_secret. $sign . $app_secret));
	}
	/**
	 * xml字符串解析为数组，xml字符串如<orders><order orderid="97913707" merchantid="6619" 
	 * paysum="32.90" isdeleted="0" ispaid="0" status="1" merchantorderid="" /></orders>
	 * <br/>如果不是xml字符串，不解析。<br/>原如果解析失败，抛出Execption异常
	 * @param string $xmlString xml字符串
	 * @param array $resultarr 解析后的结果数组
	 */
	function parseXmlString(&$xmlString,array &$arr){
		$xmlString=trim($xmlString);
		if(strpos($xmlString,'<')!==0) return false; //simple test is xml
		try{
		  $root =new SimpleXMLElement($xmlString);
		  $resultarr=array();
		  $this->__fillArray($root,$resultarr);
		  if(strpos($xmlString,'<error_response>')===false)
		     $arr['response']=$resultarr; //set taobao return val form
		  else 
		     $arr['error_response']=$resultarr; //set taobao error
		  return true;
		}catch (Exception $e){
			throw new Exception('xml parse error:' . $e->getMessage());
		}
	}	
	//----private function ------//
	private function __fillArray($root,array &$resultarr){
		if(! $root) return;
		$rootarr=get_object_vars($root);
		$isList=false;
		foreach ($rootarr as $key=>$val) {
			if($key=='@attributes'){
				$isList=$val['list'];   //is array
			} 
			else if(is_object($val)){
				$newarr=array();
				$this->__fillArray($val,$newarr);
				if($isList)
				  $resultarr[$key]=array($newarr);   //one element array
				else
				   $resultarr[$key]=$newarr;
			}
			else if(is_array($val) && count($val)>0){//is array
				$valarr=array();
				for($i=0;$i<count($val);$i++){
					$newarr=array();
					$this->__fillArray($val[$i],$newarr);
					$valarr[]=$newarr;
				}
				$resultarr[$key]=$valarr;
			}
			else
			 $resultarr[$key]=$val;
		}		
	}
	
}

